package com.project.gateway.route.filter;


import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.project.gateway.api.ResponseFailureResult;
import com.project.gateway.log.GatewayLog;
import com.project.gateway.util.IpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;


@Component
public class GatewayLogFilter implements GlobalFilter, Ordered {

    public final static Logger log = LoggerFactory.getLogger(GatewayLogFilter.class);


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        ServerHttpRequest request = exchange.getRequest();
        GatewayLog gatewayLog = new GatewayLog();

        ServerHttpRequestDecorator requestDecorator = new ServerHttpRequestDecorator(request){
            @Override
            public Flux<DataBuffer> getBody() {
                return super.getBody().map(dataBuffer -> {
                    ByteBuffer byteBuffer = dataBuffer.asByteBuffer();
                    //对请求进行日志记录
                    recordRequestLog(exchange,request,byteBuffer,gatewayLog);
//                    DataBufferFactory factory = dataBuffer.factory();
                    return dataBuffer;
                });
            }
        };

        ServerHttpResponse response = exchange.getResponse();
        DataBufferFactory bufferFactory = response.bufferFactory();
        ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(response) {

            /**
             * 当请求微服务响应回来时，会触发该方法writeWith
             * @param body 响应回来的响应体
             */
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {

                Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
                //由请求微服务报错，默认为html/text格式
                response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
                return super.writeWith(fluxBody.map(dataBuffer -> {
                    ByteBuffer byteBuffer = dataBuffer.asByteBuffer();
                    //对响应进行日志记录
                    recordResponseLog(response,byteBuffer,gatewayLog);
                    //访问日志记录
                    log.info(gatewayLog.toString());
                    //如果响应回来的是500，则进行统一服务降级
                    if(HttpStatus.INTERNAL_SERVER_ERROR == response.getStatusCode()){
                        //释放掉内存
                        DataBufferUtils.release(dataBuffer);
                        byte[] newResponseData = new byte[0];
                        try {
                            newResponseData = new ObjectMapper().writeValueAsBytes(new ResponseFailureResult());
                        } catch (JsonProcessingException e) {
                            e.printStackTrace();
                        }
                        //记得设置浏览器接收的字节长度，如果长度不够，会出现浏览器一直等待服务器响应完数据。其实服务器就已经关闭了。
                        response.getHeaders().setContentLength(newResponseData.length);
                        return bufferFactory.wrap(newResponseData);
                    }
                    return dataBuffer;
                }));
            }
        };
        //如果请求微服务报错了，则将响应处理，并替换
        return chain.filter(exchange.mutate().request(requestDecorator).response(responseDecorator).build());
    }

    @Override
    public int getOrder() {
        // 必须小于等于-2，否则无法获取相应结果
        return -2;
    }

    /**
     * 获取路由信息
     * @param exchange
     * @return
     */
    private Route getGatewayRoute(ServerWebExchange exchange) {
        return exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
    }

    private void recordRequestLog(ServerWebExchange exchange,ServerHttpRequest request,
                                  ByteBuffer byteBuffer,GatewayLog gatewayLog){

        byte[] originResponseData = new byte[byteBuffer.limit()];
        byteBuffer.mark();
        byteBuffer.get(originResponseData);
        byteBuffer.reset();
        String requestBody = new String(originResponseData, StandardCharsets.UTF_8);
        //日志记录请求体
        gatewayLog.setRequestBody(requestBody);

        // 请求路径
        String requestPath = request.getPath().pathWithinApplication().value();
        // 获取路由信息
        Route route = getGatewayRoute(exchange);
        String ipAddress = IpUtil.getIpAddr(request);
        //记录此次访问的源
        gatewayLog.setOrigin(request.getHeaders().getOrigin());
        //记录此次访问的请求协议
        gatewayLog.setSchema(request.getURI().getScheme());
        //记录此次访问的请求方式
        gatewayLog.setRequestMethod(request.getMethodValue());
        //记录此次访问的请求路径
        gatewayLog.setRequestPath(requestPath);
        //记录此次访问的请求的服务
        gatewayLog.setTargetServer(route.getUri().toString());
        //记录此次访问的请求的开始时间
        gatewayLog.setRequestTime(new Date().getTime());
        gatewayLog.setIp(ipAddress);
        //记录此次访问的路由信息
        gatewayLog.setRouteConfig(JSON.toJSONString(route));
        Map<String, Object> headers = new HashMap<>();
        for (String key : request.getHeaders().keySet()) {
            headers.put(key, request.getHeaders().getFirst(key));
        }
        //记录此次访问的请求头信息
        gatewayLog.setHeaders(JSON.toJSONString(headers));

        if (request.getHeaders().getContentType() != null) {
            gatewayLog.setRequestContentType(request.getHeaders().getContentType().toString());
        }

    }
    private void recordResponseLog(ServerHttpResponse response,ByteBuffer byteBuffer,GatewayLog gatewayLog){
        byte[] originResponseData = new byte[byteBuffer.limit()];
        byteBuffer.mark();
        byteBuffer.get(originResponseData);
        byteBuffer.reset();
        String responseBodyJson = new String(originResponseData, Charset.forName("UTF-8"));
        //记录此次访问的响应状态码
        gatewayLog.setResponseState(response.getStatusCode().toString());
        gatewayLog.setResponseTime(new Date().getTime());
        gatewayLog.setExecuteTime(gatewayLog.getResponseTime()-gatewayLog.getRequestTime());
        gatewayLog.setResponseData(responseBodyJson);
    }

}